package com.webonn.frame.core;

import com.codahale.metrics.Histogram;
import com.codahale.metrics.MetricRegistry;
import com.webonn.frame.conf.MyConfiguration;
import com.webonn.frame.db.MyDao;
import com.webonn.frame.util.EntityError;
import org.hibernate.validator.constraints.NotEmpty;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;

import javax.validation.constraints.NotNull;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


/**
 * Created by Administrator on 2016/5/10.
 */
public class TokenController {
    private final static int EXPIRATION_MINUTES = 60;     // 1 hour
    private final static int REAPER_PERIOD = 10;           // 10 minutes
    private final static SecureRandom random = new SecureRandom();
    private final static ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1);
    private final static Map<String, TokenEntry> tokenMap = new ConcurrentHashMap<>();
    private static TokenController theInstance;
    public static final Histogram tokenCount = new MetricRegistry().histogram("ActiveToken");

    @Autowired
    private MyDao mydao;
    private String user_role_sql;

    public boolean validUserRole(String userId, String role) {
        List<String> roles = mydao.listIds(user_role_sql, new Object[]{userId, role});
        return Objects.nonNull(roles) && roles.size() > 0;
    }

    private TokenController() {
        executor.schedule((Runnable) TokenController::clearExpiredTokens, REAPER_PERIOD, TimeUnit.SECONDS);
    }

    public void init(MyConfiguration conf) {
        this.user_role_sql = conf.getUser_role_sql();
    }

    /**
     * Get singleton instance
     *
     * @return the instance
     */
    public static TokenController getInstance() {
        if (theInstance == null) {
            theInstance = new TokenController();
        }
        return theInstance;
    }

    static void clearExpiredTokens() {
        if (!tokenMap.isEmpty()) {
            tokenMap.entrySet().removeIf(entry -> entry.getValue().expired());
        }
        executor.schedule((Runnable) TokenController::clearExpiredTokens, REAPER_PERIOD, TimeUnit.SECONDS);
    }


    /**
     * Generate a token for given task
     *
     * @param entity the task
     * @return the token
     */
    public String generateTokenForEntity(@NotNull final String entity) {
        String token = new BigInteger(130, random).toString(32);
        tokenMap.put(token, new TokenEntry(entity));
        tokenCount.update(tokenMap.size());
        return token;
    }

    /**
     * Revoke token for given task
     *
     * @param entity the task
     * @param token  the token
     */
    public void revokeTokenForEntity(@NotNull final String entity, @NotEmpty final String token) {
        // Remove token
        if (tokenMap.containsKey(token)) {
            if (tokenMap.get(token).getEntity().equals(entity)) {
                tokenMap.remove(token);
                tokenCount.update(tokenMap.size());
                return;
            }
        }
        throw new EntityError("TokenAuth not found", 404);
    }

    /**
     * Get associated task for the token
     *
     * @param token the token
     * @return the task
     */
    public String getEntityFromToken(@NotEmpty final String token) {
        if (!isValidToken(token)) {
            return null;
        }
        return tokenMap.get(token).getEntity();
    }

    /**
     * Validate token and associated task
     *
     * @param token the token
     * @param id    the task or task id
     * @return true if the token is valid for the task
     */
    public boolean isValidTokenForEntity(@NotEmpty final String token, @NotEmpty final Object id) {
        return isValidToken(token) && getEntityFromToken(token).equals(id.toString());
    }

    public boolean isValidToken(@NotEmpty final String token) {
        if (!tokenMap.containsKey(token)) {
            return false;
        }
        TokenEntry entry = tokenMap.get(token);
        boolean ret = !entry.expired();
        if (ret) {
            entry.refresh();
        }
        return ret;
    }

    private static class TokenEntry {
        private final String entity;
        private String role;
        private DateTime expiration;

        TokenEntry(@NotNull String entity) {
            this.entity = entity;
            refresh();
        }

        void refresh() {
            expiration = new DateTime().plusMinutes(EXPIRATION_MINUTES);
        }

        boolean expired() {
            return expiration.isBeforeNow();
        }

        String getEntity() {
            return entity;
        }
    }
}
